package org.cloud.simple.helloworld;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.util.Assert;

public final class OpenUtil {

	/** 支付宝账户正则 */
	/**
	 * 支付宝账户正则
	 * <p>^1\d{10}$|^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$ （支持手机或邮箱）</p>
	 * <p>^1\d{10}$ （仅手机）</p>
	 */
	public static final String ALI_REG = "^1\\d{10}$";
	public static final long FILE_SIZE = 5 * 1024 * 1024;

	private OpenUtil() {
	}

	/**
	 * 字符串转为字节数组
	 * 
	 * @see 该方法默认以ISO-8859-1转码
	 * @see 若想自己指定字符集,可以使用<code>getBytes(String str, String charset)</code>方法
	 */
	public static byte[] getBytes(String data) {
		return getBytes(data, "ISO-8859-1");
	}

	/**
	 * 字符串转为字节数组
	 * 
	 * @see 如果系统不支持所传入的<code>charset</code>字符集,则按照系统默认字符集进行转换
	 */
	public static byte[] getBytes(String data, String charset) {
		data = (null == data ? "" : data);
		if (StringUtils.isBlank(charset)) {
			return data.getBytes();
		}
		try {
			return data.getBytes(charset);
		} catch (UnsupportedEncodingException e) {
			LogUtil.getAppLogger().error("将字符串[" + data + "]转为byte[]时发生异常:系统不支持该字符集[" + charset + "]");
			return data.getBytes();
		}
	}

	/**
	 * 获取前一天日期yyyyMMdd
	 * 
	 * @see 经测试，针对闰年02月份或跨年等情况，该代码仍有效。测试代码如下
	 * @see calendar.set(Calendar.YEAR, 2013);
	 * @see calendar.set(Calendar.MONTH, 0);
	 * @see calendar.set(Calendar.DATE, 1);
	 * @see 测试时，将其放到<code>calendar.add(Calendar.DATE, -1);</code>前面即可
	 * @return 返回的日期格式为yyyyMMdd
	 */
	public static String getYestoday() {
		Calendar calendar = Calendar.getInstance();
		calendar.add(Calendar.DATE, -1);
		return new SimpleDateFormat("yyyyMMdd").format(calendar.getTime());
	}

	/**
	 * 获取Map中的属性
	 * 
	 * @see 由于Map.toString()打印出来的参数值对,是横着一排的...参数多的时候,不便于查看各参数值
	 * @see 故此仿照commons-lang3.jar中的ReflectionToStringBuilder.toString()编写了本方法
	 * @return String key11=value11 \n key22=value22 \n key33=value33 \n......
	 */
	public static String buildStringFromMap(Map<String, String> map) {
		StringBuilder sb = new StringBuilder();
		sb.append(map.getClass().getName()).append("@").append(map.hashCode()).append("[");
		for (Map.Entry<String, String> entry : map.entrySet()) {
			sb.append("\n").append(entry.getKey()).append("=").append(entry.getValue());
		}
		return sb.append("\n]").toString();
	}

	/**
	 * 获取Map中的属性
	 * 
	 * @see 该方法的参数适用于打印Map<String, String[]>的情况
	 * @see 由于Map.toString()打印出来的参数值对,是横着一排的...参数多的时候,不便于查看各参数值
	 * @see 故此仿照commons-lang3.jar中的ReflectionToStringBuilder.toString()编写了本方法
	 * @return String key11=value11 \n key22=value22 \n key33=value33 \n......
	 */
	public static String buildStringFromMapWithStringArray(Map<String, String[]> map) {
		StringBuilder sb = new StringBuilder();
		sb.append(map.getClass().getName()).append("@").append(map.hashCode()).append("[");
		for (Map.Entry<String, String[]> entry : map.entrySet()) {
			sb.append("\n").append(entry.getKey()).append("=").append(Arrays.toString(entry.getValue()));
		}
		return sb.append("\n]").toString();
	}

	/**
	 * HttpServletRequest参数值转为JavaBean
	 * 
	 * @see 该方法目前只能处理所有属性均为String的JavaBean
	 * @see 且只能处理当前类,暂不能处理父类和子类
	 * @see 且类属性只能是String
	 * @create Dec 17, 2015 4:44:47 PM
	 * @author 玄玉<http://blog.csdn.net/jadyer>
	 */
	public static <T> T requestToBean(HttpServletRequest request, Class<T> beanClass) {
		try {
			T bean = beanClass.newInstance();
			// getFields()能获取到父类和子类中所有public的属性
			for (Field field : beanClass.getDeclaredFields()) {
				// 构造setter方法
				String methodName = "set" + StringUtils.capitalize(field.getName());
				try {
					// 执行setter方法
					beanClass.getMethod(methodName, String.class).invoke(bean,
							URLDecoder.decode(request.getParameter(field.getName()), "UTF-8"));
				} catch (Exception e) {
					// ignore exception
					continue;
				}
			}
			return bean;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static <T> Map<String, Object> beanToMap(T bean) {
		Map<String, Object> map = new HashMap<>();
		try {
			Field fields[] = bean.getClass().getDeclaredFields();
			Field.setAccessible(fields, true);
			for (Field field : fields) {
				if (null != field.get(bean)) {
					map.put(field.getName(), field.get(bean));
				}
			}
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return map;
	}

	public static String moneyYuanToFen(BigDecimal amount) {
		if (null == amount) {
			return moneyYuanToFen("0");
		}
		return moneyYuanToFen(amount.toPlainString());
	}

	/**
	 * 金额元转分
	 * @update 修正入参为负数时,转换错误的bug.
	 * @see 注意:该方法可处理贰仟万以内的金额,且若有小数位,则不限小数位的长度
	 * @see 注意:如果你的金额达到了贰仟万以上,则不推荐使用该方法,否则计算出来的结果会令人大吃一惊
	 * @param amount
	 *            金额的元进制字符串
	 * @return String 金额的分进制字符串
	 */
	public static String moneyYuanToFen(String amount) {
		if (StringUtils.isBlank(amount)) {
			return "0";
		}
		return String.valueOf("");
	}

	/**
	 * 金额元转分
	 * 
	 * @deprecated 该方法只支持正数,不支持负数.如有该功能需要请使用{@code BigDecimal}和{@code RoundingMode}
	 * @see 该方法会将金额中小数点后面的数值,四舍五入后只保留两位....如12.345-->12.35
	 * @see 注意:该方法可处理贰仟万以内的金额
	 * @see 注意:如果你的金额达到了贰仟万以上,则非常不建议使用该方法,否则计算出来的结果会令人大吃一惊
	 * @param amount
	 *            金额的元进制字符串
	 * @return String 金额的分进制字符串
	 */
	@Deprecated
	public static String moneyYuanToFenByRound(String amount) {
		if (StringUtils.isBlank(amount)) {
			return amount;
		}
		if (-1 == amount.indexOf(".")) {
			return Integer.parseInt(amount) * 100 + "";
		}
		int money_fen = Integer.parseInt(amount.substring(0, amount.indexOf("."))) * 100;
		String pointBehind = (amount.substring(amount.indexOf(".") + 1));
		if (pointBehind.length() == 1) {
			return money_fen + Integer.parseInt(pointBehind) * 10 + "";
		}
		int pointString_1 = Integer.parseInt(pointBehind.substring(0, 1));
		int pointString_2 = Integer.parseInt(pointBehind.substring(1, 2));
		// 下面这种方式用于处理pointBehind=245,286,295,298,995,998等需要四舍五入的情况
		if (pointBehind.length() > 2) {
			int pointString_3 = Integer.parseInt(pointBehind.substring(2, 3));
			if (pointString_3 >= 5) {
				if (pointString_2 == 9) {
					if (pointString_1 == 9) {
						money_fen = money_fen + 100;
						pointString_1 = 0;
						pointString_2 = 0;
					} else {
						pointString_1 = pointString_1 + 1;
						pointString_2 = 0;
					}
				} else {
					pointString_2 = pointString_2 + 1;
				}
			}
		}
		if (pointString_1 == 0) {
			return money_fen + pointString_2 + "";
		} else {
			return money_fen + pointString_1 * 10 + pointString_2 + "";
		}
	}

	public static String moneyFenToYuan(Integer amount) {
		if(null == amount){
			return moneyFenToYuan("0");
		}
		return moneyFenToYuan(String.valueOf(amount));
	}

	/**
	 * 金额分转元
	 * 
	 * @update 注意:如果传入的参数中含小数点,<s>则直接原样返回.</s><strong>则舍弃小数点后的位数.</strong>
	 * @see 该方法返回的金额字符串格式为<code>00.00</code>,其整数位有且至少有一个,小数位有且长度固定为2
	 * @param amount
	 *            金额的分进制字符串
	 * @return String 金额的元进制字符串
	 */
	public static String moneyFenToYuan(String amount) {
		if (StringUtils.isBlank(amount)) {
			return positiveMoneyFenToYuan("0");
		}
		if(amount.startsWith("-")){
			BigDecimal am = new BigDecimal(amount).abs();
			return am.compareTo(BigDecimal.ZERO) == 0 ? positiveMoneyFenToYuan("0"):"-".concat(positiveMoneyFenToYuan(am.toPlainString()));
		}
		return positiveMoneyFenToYuan(amount);
	}
	
	private static String positiveMoneyFenToYuan(String amount) {
		if (StringUtils.isBlank(amount)) {
			return amount;
		}
		if (amount.indexOf(".") > -1) {
			return positiveMoneyFenToYuan(amount.substring(0,amount.indexOf('.')));
		}
		if (amount.length() == 1) {
			return "0.0" + amount;
		} else if (amount.length() == 2) {
			return "0." + amount;
		} else {
			return amount.substring(0, amount.length() - 2) + "." + amount.substring(amount.length() - 2);
		}
	}

	/**
	 * 隐藏部分敏感字符
	 * 
	 * @create Dec 19, 2015 9:51:56 PM
	 * @author 玄玉<http://blog.csdn.net/jadyer>
	 */
	public static String hideString(String data) {
		return data.substring(0, 6) + "******" + data.substring(data.length() - 6);
	}

	/**
	 * 判断当前时间是否小于（申请时间+24小时）
	 * 
	 * @param applyTime
	 *            申请时间
	 */
	public static boolean greaterThanTime(Date applyTime) {
		GregorianCalendar now = new GregorianCalendar();
		Date currentTime = now.getTime();
		now.setTime(applyTime);
		now.add(Calendar.HOUR_OF_DAY, 24);
		return currentTime.before(now.getTime());
	}

	/**
	 * 构建调用账务系统时传输的CallId
	 * 
	 * @create Apr 12, 2016 5:59:06 PM
	 * @author 玄玉<http://blog.csdn.net/jadyer>
	 */
	public static String buildCallIdForCCS() {
		return "open" + DateFormatUtils.format(new Date(), "yyyyMMddHHmmss") + RandomStringUtils.randomNumeric(5);
	}

	private static final String IMG_REGEX = ".+(.jpeg|.jpg|.gif|.png)$";
	public static final String FILE_REGEX = ".+(.jpeg|.jpg|.gif|.png|.pdf|.doc|.docx|.txt)$";

	/**
	 * 匹配是否为图片格式
	 * @param filePath
	 * @return
	 */
	public static boolean isImage(String filePath) {
		Assert.hasText(filePath, "filePath is null");
		return isMatcher(filePath, IMG_REGEX);
	}

	/**
	 * 匹配格式
	 * @param str 文本
	 * @param regex 正则
	 * @return
	 */
	public static boolean isMatcher(String str, String regex) {
		Assert.hasText(str, "str is null");
		Assert.hasText(regex, "regex is null");
		Pattern p = Pattern.compile(regex);
		Matcher m = p.matcher(str.toLowerCase());
		return m.find();
	}
}